/** This class decodes and encodes binary files to/from BASE64 
 *  @author Copyright (c) 1999, Bob Withers - bwit@pobox.com
 *  @author Copyright (c) 2005, chrmac@gmx.de */

package com.jtalker.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.swing.JFileChooser;
import javax.swing.JFrame;

public class BASE64Handler {
	private String lastError = null;
	private static int MAX_FILE_SIZE = 307200; // Max 300kb
	private JFrame parentFrame;

	/** Constructor */
	public BASE64Handler(JFrame parentFrame) {
		this.lastError = null;
		this.parentFrame = parentFrame;
	}

	/** Gets the last error message */
	public String getLastError() {
		return this.lastError;
	}

	/**
	 * Lets User select a File and gets it as BASE64 encoded String
	 * 
	 * @return A File-Object with name and data or null
	 */
	public EncodedFileObject getEncodedFile() {
		try {
			JFileChooser fc = new JFileChooser();
			// int returnVal = fc.showOpenDialog(this);
			int returnVal = fc.showOpenDialog(this.parentFrame);

			if (returnVal == JFileChooser.APPROVE_OPTION) {

				// Open File
				File file = fc.getSelectedFile();
				// Check for file size!!! Max ~ 300kb Server doesn't support bigger files

				int fileSize = (int) (new FileInputStream(file).getChannel().size());
				if (fileSize > MAX_FILE_SIZE) {
					this.lastError = "File is too big! Max size is 300 kB.";
					return null;
				}
				// String tmpFilename = file.getAbsolutePath();
				return new EncodedFileObject(file.getName(), BASE64Handler.getString(BASE64Handler
						.encode(getBytesFromFile(file))));
			}

		} catch (Exception e) {
			this.lastError = "Can't load/encode file: " + e.getMessage();
			return null;
		}

		this.lastError = "Action cancelled";
		return null;
	}

	/** Lets User save a BASE64 encoded String as File with given file name */
	public boolean saveDecodedData(String data, String filename) {
		if (data == null || data.equals("")) {
			this.lastError = "No Data found.";
			return false;
		}

		if (filename == null){
			filename = "unknown";
		}
		try {
			JFileChooser fc = new JFileChooser();
			fc.setDialogType(JFileChooser.SAVE_DIALOG);
			fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			// fc.setFileFilter(filter);
			fc.setSelectedFile(new File(filename));

			int returnVal = fc.showSaveDialog(this.parentFrame);

			if (returnVal == JFileChooser.APPROVE_OPTION) {
				// Open File
				File file = fc.getSelectedFile();
				// String tmpFilename = file.getAbsolutePath();
				FileOutputStream fos = new FileOutputStream(file);
				// BufferedWriter out = new BufferedWriter(new FileWriter(tmpFilename));
				fos.write(BASE64Handler.decode(BASE64Handler.getBinaryBytes(data)));
				fos.close();
				return true;
			}

		} catch (Exception e) {
			e.printStackTrace();
			this.lastError = "Can't save/decode file: " + e.getMessage();
			return false;
		}

		this.lastError = "Action cancelled";
		return false;
	}

	/** Returns the contents of the file in a byte array. */
	public static byte[] getBytesFromFile(File file) throws IOException {
		InputStream is = new FileInputStream(file);
		// Get the size of the file
		long length = file.length();
		// You cannot create an array using a long type.
		// It needs to be an int type.
		// Before converting to an int type, check
		// to ensure that file is not larger than Integer.MAX_VALUE.
		if (length > Integer.MAX_VALUE) {
			// File is too large
			return null;
		}

		// Create the byte array to hold the data
		byte[] bytes = new byte[(int) length];

		// Read in the bytes
		int offset = 0;

		int numRead = 0;
		while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
			offset += numRead;
		}

		// Ensure all the bytes have been read in
		if (offset < bytes.length) {
			throw new IOException("Could not completely read file " + file.getName());
		}

		// Close the input stream and return bytes
		is.close();
		return bytes;
	}

	/** Returns a BASE64 encoded String */
	public static String encode(String data) {
		return (getString(encode(getBinaryBytes(data))));
	}

	/** Returns a BASE64 encoded byte Array */
	public static byte[] encode(byte[] data) {
		int c;
		int len = data.length;
		StringBuffer ret = new StringBuffer(((len / 3) + 1) * 4);
		for (int i = 0; i < len; ++i) {
			c = (data[i] >> 2) & 0x3f;
			ret.append(cvt.charAt(c));
			c = (data[i] << 4) & 0x3f;
			if (++i < len) {
				c |= (data[i] >> 4) & 0x0f;
			}
			ret.append(cvt.charAt(c));
			if (i < len) {
				c = (data[i] << 2) & 0x3f;
				if (++i < len) {
					c |= (data[i] >> 6) & 0x03;
				}
				ret.append(cvt.charAt(c));
			} else {
				++i;
				ret.append((char) fillchar);
			}

			if (i < len) {
				c = data[i] & 0x3f;
				ret.append(cvt.charAt(c));
			} else {
				ret.append((char) fillchar);
			}
		}

		return getBinaryBytes(ret.toString());
	}

	/** Decodes a BASES64 encoded String */
	public static String decode(String data) {
		return (getString(decode(getBinaryBytes(data))));
	}

	/** Decodes a BASES64 encoded byte Array */
	public static byte[] decode(byte[] data) {
		int c;
		int c1;
		int len = data.length;
		StringBuffer ret = new StringBuffer((len * 3) / 4);
		for (int i = 0; i < len; ++i) {
			c = cvt.indexOf(data[i]);
			++i;
			c1 = cvt.indexOf(data[i]);
			c = ((c << 2) | ((c1 >> 4) & 0x3));
			ret.append((char) c);
			if (++i < len) {
				c = data[i];
				if (fillchar == c){
					break;
				}
				c = cvt.indexOf((char) c);
				c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf);
				ret.append((char) c1);
			}

			if (++i < len) {
				c1 = data[i];
				if (fillchar == c1){
					break;
				}
				c1 = cvt.indexOf((char) c1);
				c = ((c << 6) & 0xc0) | c1;
				ret.append((char) c);
			}
		}

		return getBinaryBytes(ret.toString());
	}

	/** Converts a byte Array to String */
	private static String getString(byte[] arr) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < arr.length; ++i) {
			buf.append((char) arr[i]);
		}
		return buf.toString();
	}

	/** Converts a String to a byte Array */
	private static byte[] getBinaryBytes(String str) {
		byte[] b = new byte[str.length()];
		for (int i = 0; i < b.length; ++i) {
			b[i] = (byte) str.charAt(i);
		}
		return b;
	}

	private static final int fillchar = '=';

	// 00000000001111111111222222
	// 01234567890123456789012345
	// 22223333333333444444444455
	// 67890123456789012345678901
	// 555555556666
	// 234567890123
	private static final String cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/";

	public static void main(String[] args) {
		// Only for Debug
		BASE64Handler myHandler = new BASE64Handler(null);
		EncodedFileObject tmpFile = myHandler.getEncodedFile();
		System.out.println("FILE: " + tmpFile.filename);
		myHandler.saveDecodedData(tmpFile.data, tmpFile.filename);
	}

	// Helper Class
	public class EncodedFileObject {
		public EncodedFileObject(String fileName, String data) {
			this.filename = fileName;
			this.data = data;
		}

		public String filename = null;
		public String data = null;
	}

}
